library(tidyverse)
library(ggthemes)
library(scales)
library(forcats)
library(jpeg)
filename <- 'data/faces.csv'
faces <- read_csv(filename)
Parsed with column specification:
cols(
  cat_id = col_character(),
  cat_name = col_character(),
  artist_id = col_character(),
  artist_name = col_character(),
  genres = col_character(),
  face_count = col_integer(),
  face_id = col_character(),
  face_pitch = col_double(),
  face_yaw = col_double(),
  face_roll = col_double(),
  face_gender = col_character(),
  face_glasses = col_character(),
  face_height = col_integer(),
  face_width = col_integer(),
  face_beard = col_double(),
  face_moustache = col_double(),
  face_sideburns = col_double(),
  face_smile = col_double()
)
with_faces <- faces %>% filter(face_count > 0)
faces <- faces %>% mutate(has_face = face_count > 0)
with_faces <- faces %>% filter(face_count > 0)
unique_artists <- faces %>% distinct(cat_id, artist_id, .keep_all = TRUE)

Artist Counts

unique_artists %>%
  ggplot(aes(x = fct_rev(fct_infreq(cat_name)))) +
  coord_flip() +
  geom_bar() +
  scale_y_continuous(labels = comma) +
  labs(title = "Artists Per Category", y = "") +
  theme_fivethirtyeight()

byCat <- unique_artists %>% group_by(cat_name) %>% mutate(cat_count = n()) %>%
  arrange(-cat_count) %>%
  group_by(cat_name, has_face) %>% summarise(count = n(), per = count / cat_count[1], cat_count = cat_count[1]) %>% arrange(cat_count)
byCat %>% filter(has_face == TRUE) %>%
  ggplot(aes(x = fct_inorder(cat_name), y = count )) + 
  coord_flip() +
  geom_bar(stat = "identity") +
  scale_y_continuous(labels = comma) +
  labs(title = "Count of Artists with Faces", y = "") +
  theme_fivethirtyeight()

byCat %>% 
  ggplot(aes(x = fct_inorder(cat_name), y = per, fill = has_face)) + 
  coord_flip() +
  scale_y_continuous(labels = percent) +
  geom_bar(stat = "identity") + 
  labs(x = "", y = "", title = "Percent Artists with Face by Category") +
  theme_fivethirtyeight()

Face Counts

faces %>%
  ggplot(aes(x = (face_count))) +
  geom_histogram(binwidth = 1.0) +
  scale_y_continuous(labels = comma) +
  scale_x_continuous(breaks = seq(0, 15, 1)) +
  theme_fivethirtyeight() +
  labs(title = "Distribution of Face Count", x = "Number of Faces in Artist Image")

unique_artists %>%
  ggplot(aes(x = (face_count))) +
  geom_histogram(binwidth = 1.0) +
  scale_y_continuous(labels = comma) +
  scale_x_continuous(breaks = seq(0, 15, 1)) +
  theme_fivethirtyeight() +
  labs(title = "Distribution of Face Count for Artists", x = "Number of Faces in Artist Image")

Lots of no-faces detected and solo artists.

max(unique_artists$face_count)
[1] 14

Face Count by Genre

byCatFaceCount <- unique_artists %>% filter(has_face == TRUE) %>%
  group_by(cat_name) %>% summarise(mean_face_count = mean(face_count), median_face_count = median(face_count))
  
unique_artists %>% filter(has_face == TRUE) %>% #filter(cat_id == "metal") %>%
  ggplot(aes(x = face_count)) +
  #geom_histogram(binwidth = 1.0) +
  #geom_histogram(aes(y=..count../sum(..count..)), binwidth = 1.0) + 
  geom_histogram(aes(y = (..count..)/tapply(..count..,..PANEL..,sum)[..PANEL..]), binwidth = 1.0) + 
  scale_y_continuous(labels = percent) +
  scale_x_continuous(breaks = seq(1, 14, 2)) +
  facet_wrap(~ cat_name) + 
  labs(title = "Distribution of Face Count for Artists by Category", x = "Number of Faces in Artist Image") +
  theme_fivethirtyeight()

Cool!

So Country and Hip Hop artists almost always have solo images.

But you can’t have a metal, punk, or rock band without at least a few people.

Gender

with_faces %>%
  ggplot(aes(x = 1, fill = face_gender)) + 
  scale_y_continuous(labels = comma) +
  scale_x_continuous(labels = c()) +
  geom_bar() + 
  labs(x = "", y = "", title = "Count of Faces by Gender") +
  theme_fivethirtyeight()

with_faces %>%
  ggplot(aes(x = 1, fill = face_gender)) + 
  scale_y_continuous(labels = percent) +
  scale_x_continuous(labels = c()) +
  geom_bar(aes(y = (..count..)/sum(..count..)), position = "dodge") + 
  labs(x = "", y = "", title = "Percent of Faces by Gender") +
  theme_fivethirtyeight()

Lots of Dudes in this data!

Gender by Category

with_faces %>% #filter(face_gender == 'female') %>% 
  ggplot(aes(x = face_gender))  +
  geom_bar() +
  #geom_histogram(aes(y=..count../sum(..count..)), binwidth = 1.0) + 
  #geom_histogram(aes(y = (..count..)/tapply(..count..,..PANEL..,sum)[..PANEL..]), binwidth = 1.0) + 
  scale_y_continuous(labels = comma) +
  #scale_x_continuous(breaks = seq(1, 14, 2)) +
  facet_wrap(~ cat_name) + 
  labs(title = "Gender of Faces by Category", x = "") +
  theme_fivethirtyeight()

with_faces %>% #filter(face_gender == 'female') %>% 
  ggplot(aes(x = face_gender))  +
  #geom_bar() +
  #geom_histogram(aes(y=..count../sum(..count..)), binwidth = 1.0) + 
  geom_bar(aes(y = (..count..)/tapply(..count..,..PANEL..,sum)[..PANEL..]) ) + 
  scale_y_continuous(labels = percent) +
  #scale_x_continuous(breaks = seq(1, 14, 2)) +
  facet_wrap(~ cat_name) + 
  labs(title = "Gender Percents of Faces by Category", x = "") +
  theme_fivethirtyeight()

Glasses

with_faces %>%
  ggplot(aes(x = face_glasses, fill = face_glasses)) + 
  scale_y_continuous(labels = percent) +
  geom_bar(aes(y = (..count..)/sum(..count..)), position = "dodge") + 
  labs(x = "", y = "", title = "Percentage of Glasses on Faces", fill = "Glasses Type") +
  theme_fivethirtyeight()

Swimming Goggles ?? That can’t be right.

face_ind <- c(1,2,3)
filtered_faces <- with_faces %>% filter(face_glasses == 'SwimmingGoggles') %>% filter(face_count == 1) 
row <- filtered_faces[face_ind,]
filename <- paste("data/imgs/", row$cat_id, "/", row$artist_id, ".jpg", sep="")
knitr::include_graphics(filename)

Well, ok. Maybe not.

with_faces %>% filter(!(face_glasses == 'NoGlasses')) %>% filter(!(face_glasses == 'SwimmingGoggles')) %>%
  ggplot(aes(x = face_glasses, fill = face_glasses)) + 
  scale_y_continuous(labels = percent) +
  geom_bar(aes(y = (..count..)/tapply(..count..,..PANEL..,sum)[..PANEL..]) ) + 
  facet_wrap(~ cat_name) + 
  labs(x = "", y = "", title = "Sun Glasses vs Reading Glasses by Category", fill = "Glasses Type") +
  theme_fivethirtyeight()

3D Face Pose

There are 3 values recorded for face pose: Yaw, Roll, and Pitch.

Most explainations of these features only explain it for Aircraft, but here is my understanding for human faces:

Unfortunately, Pitch isn’t yet computed - so we can only look

Lets look at Yaw and Roll:

Yaw

with_faces %>%
  ggplot(aes(x = face_yaw)) +
  geom_histogram(binwidth = 2) +
  scale_y_continuous(labels = comma) +
  #scale_x_continuous(breaks = seq(0, 15, 1)) +
  theme_fivethirtyeight() +
  labs(title = "Distribution of Yaw Metric", x = "")

Mean:

mean(with_faces$face_yaw)
[1] -1.516755

Median:

median(with_faces$face_yaw)
[1] -1.9

So there is a slight trend of the nose pointing to the left … or at least that is what the algorithm is detecting. Could be a bias!

But really, as we see below, -1 or -2 is really not visibly noticable as a head turn.

Yaw Examples

Light Negative Yaw

Faces with yaw between -1 and -1.8

face_ind <- c(1,2,3)
filtered_faces <- with_faces %>% filter(face_count == 1) %>% filter(face_yaw < -1) %>% filter(face_yaw > -1.8)
row <- filtered_faces[face_ind,]
filename <- paste("data/imgs/", row$cat_id, "/", row$artist_id, ".jpg", sep="")
knitr::include_graphics(filename)

Medium Negative Yaw

Faces with Yaw between -10 and -15

med_neg_yaw <- with_faces %>% filter(face_count == 1) %>% filter(face_yaw < -10) %>% filter(face_yaw > -15)
row <- med_neg_yaw[face_ind,]
filename <- paste("data/imgs/", row$cat_id, "/", row$artist_id, ".jpg", sep="")
knitr::include_graphics(filename)

Medium Positive Yaw

Faces with Yaw between 10 and 15

med_pos_yaw <- with_faces %>% filter(face_count == 1) %>% filter(face_yaw > 10) %>% filter(face_yaw < 15)
row <- med_pos_yaw[face_ind,]
filename <- paste("data/imgs/", row$cat_id, "/", row$artist_id, ".jpg", sep="")
knitr::include_graphics(filename)

Heavy Positive Yaw

Faces with > 30 Yaw

filtered_faces <- with_faces %>% filter(face_count == 1) %>% filter(face_yaw > 30)
row <- filtered_faces[face_ind,]
filename <- paste("data/imgs/", row$cat_id, "/", row$artist_id, ".jpg", sep="")
knitr::include_graphics(filename)

Roll

Let’s get our Roll on.

with_faces %>%
  ggplot(aes(x = face_roll)) +
  geom_histogram(binwidth = 2) +
  scale_y_continuous(labels = comma) +
  #scale_x_continuous(breaks = seq(0, 15, 1)) +
  theme_fivethirtyeight() +
  labs(title = "Distribution of Roll Metric", x = "")

mean(with_faces$face_roll)
[1] -0.9685942
median(with_faces$face_roll)
[1] -0.9

Roll looks to be distributed pretty evenly - but there is a longer tail to the right. The summary stats indicate there is a slight drag to the left.

Lets look at some examples!

Roll Examples

Light Negative Roll

Faces with roll between -1 and -2

face_ind <- c(1,2,3)
filtered_faces <- with_faces %>% filter(face_count == 1) %>% filter(face_roll < -1) %>% filter(face_roll > -2)
row <- filtered_faces[face_ind,]
filename <- paste("data/imgs/", row$cat_id, "/", row$artist_id, ".jpg", sep="")
knitr::include_graphics(filename)

Heavy Negative Roll

Faces with roll < -20

filtered_faces <- with_faces %>% filter(face_count == 1) %>% filter(face_roll < -20)
row <- filtered_faces[face_ind,]
filename <- paste("data/imgs/", row$cat_id, "/", row$artist_id, ".jpg", sep="")
knitr::include_graphics(filename)

Heavy Positive Roll

Faces with roll > 20

filtered_faces <- with_faces %>% filter(face_count == 1) %>% filter(face_roll > 20)
row <- filtered_faces[face_ind,]
filename <- paste("data/imgs/", row$cat_id, "/", row$artist_id, ".jpg", sep="")
knitr::include_graphics(filename)

Facial Hair

Look just at Beards for a second:

with_faces %>% filter(face_gender == 'male') %>%
  ggplot(aes(x = face_beard)) + 
  geom_freqpoly(binwidth = 0.01) + 
  labs(title = "Male Beard Likelihood Values")

Interesting! So this isn’t really a continuous variable, instead its descritized.

To double check that, here are the unique values of face_beard:

sort(unique(with_faces$face_beard))
 [1] 0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0
facial_hair <- with_faces %>% tidyr::gather(key = hair_type, value = percent, face_beard, face_moustache, face_sideburns)
facial_hair %>% filter(face_gender == 'male') %>%
  ggplot(aes(x = as.factor(hair_ratio), fill = hair_type)) + 
  geom_bar(position = "dodge") + 
  scale_y_continuous(labels = comma) +
  labs(title = "Male Facial Hair Counts", fill = "Hair Type") + 
  theme_fivethirtyeight()

facial_hair %>% filter(face_gender == 'male') %>%
  ggplot(aes(x = as.factor(hair_ratio), fill = hair_type)) + 
  #geom_bar(position = "dodge") + 
  geom_bar(aes(y=..count../sum(..count..)), position = "dodge") + 
  scale_y_continuous(labels = percent) +
  labs(title = "Male Facial Hair Percents", fill = "Hair Type") + 
  theme_fivethirtyeight()

facial_hair %>% filter(face_gender == 'male') %>%
  ggplot(aes(x = as.factor(hair_ratio), fill = hair_type)) + 
  #geom_bar(position = "dodge") + 
  geom_bar(aes(y=..count../sum(..count..)), position = "dodge") + 
  scale_y_continuous(labels = percent) +
  labs(title = "Male Facial Hair Percents", fill = "Hair Type") + 
  facet_wrap(~ hair_type) +
  theme_fivethirtyeight()

What about female identified faces?

facial_hair %>% filter(face_gender == 'female') %>%
  ggplot(aes(x = hair_ratio, fill = hair_type)) + 
  geom_histogram(binwidth = 0.01) +
  labs(title = "Women don't have Facial Hair in this dataset / algorithm")

Facial Hair by Category

facial_hair %>% filter(face_gender == 'male') %>% filter(hair_type == 'face_beard') %>%
  ggplot(aes(x = as.factor(hair_ratio) )) + 
  #geom_bar(position = "dodge") + 
  #geom_bar(aes(y=..count../sum(..count..))) + 
  geom_bar(aes(y = (..count..)/tapply(..count..,..PANEL..,sum)[..PANEL..])) + 
  scale_y_continuous(labels = percent) +
  labs(title = "Beards by Category") + 
  facet_wrap(~ cat_name) +
  theme_fivethirtyeight()

filtered_faces <- with_faces %>% filter(face_count == 1) %>% filter(face_gender == 'male') %>% filter(face_beard == 1.0) 
row <- filtered_faces[face_ind,]
filename <- paste("data/imgs/", row$cat_id, "/", row$artist_id, ".jpg", sep="")
knitr::include_graphics(filename)

filtered_faces <- with_faces %>% filter(face_count == 1) %>% filter(face_gender == 'male') %>% filter(face_beard == 1.0) 

row <- filtered_faces[face_ind,]
filename <- paste("data/imgs/", row$cat_id, "/", row$artist_id, ".jpg", sep="")
knitr::include_graphics(filename)
male_hair <- with_faces %>% filter(face_gender == 'male') %>% mutate(has_facial_hair = face_beard + face_moustache > 0.4)
male_hair %>%
  ggplot(aes(x = has_facial_hair, fill = has_facial_hair))  +
  #geom_bar() +
  #geom_histogram(aes(y=..count../sum(..count..)), binwidth = 1.0) + 
  geom_bar(aes(y = (..count..)/tapply(..count..,..PANEL..,sum)[..PANEL..]) ) + 
  scale_y_continuous(labels = percent) +
  #scale_x_continuous(breaks = seq(1, 14, 2)) +
  facet_wrap(~ cat_name) + 
  labs(title = "Gender Percents of Faces by Category", x = "") +
  theme_fivethirtyeight()

Smiles

Most artists appear not to be smiling.

with_faces %>% 
  ggplot(aes(x = face_smile)) + 
  geom_histogram(binwidth = 0.1) +
  labs(title = "Smile Distribution")

Gender doesn’t seem to matter much:

with_faces %>% 
  ggplot(aes(x = face_smile, fill = face_gender)) + 
  geom_histogram(aes(y = (..count..)/tapply(..count..,..PANEL..,sum)[..PANEL..]),binwidth = 0.1 ) + 
  #geom_histogram(aes(y=..count../sum(..count..)), binwidth = 0.1, position = "dodge") +
  facet_wrap(~ face_gender) +
  scale_y_continuous(labels = percent) +
  labs(title = "Smile Distribution by Gender", y = "", x = "")

with_faces %>% mutate(is_smiling = face_smile > 0.25) %>%
  ggplot(aes(x = is_smiling, fill = is_smiling)) + 
  geom_bar(aes(y = (..count..)/tapply(..count..,..PANEL..,sum)[..PANEL..]) ) + 
  #geom_histogram(aes(y=..count../sum(..count..)), binwidth = 0.1, position = "dodge") +
  facet_wrap(~ face_gender) +
  scale_y_continuous(labels = percent) +
  labs(title = "Smile Distribution by Gender", y = "", x = "")

Image Sizes

img_details_filename <- "data/images.csv"
images <- read_csv(img_details_filename)
Parsed with column specification:
cols(
  cat = col_character(),
  artist = col_character(),
  face_num = col_integer(),
  img_width = col_integer(),
  img_height = col_integer()
)
images %>%
  ggplot(aes(x = img_width)) +
  geom_histogram(binwidth = 10) +
  labs(title = "Image Widths")

NA
summary(images$img_width)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  198.0   640.0   640.0   751.5  1000.0  1000.0 
images %>%
  ggplot(aes(x = img_height)) +
  geom_histogram(binwidth = 10) +
  labs(title = "Image Height")

NA
summary(images$img_height)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  114.0   640.0   640.0   725.7   668.0  1805.0 
images %>%
  ggplot(aes(x = img_width, y = img_height)) +
  geom_point( alpha = 1 / 10) +
  stat_density2d(aes(fill = ..level..), geom="polygon")

Face Image Sizes

face_details_filename <- "data/face_sizes.csv"
face_details <- read_csv(face_details_filename)
Parsed with column specification:
cols(
  cat_id = col_character(),
  artist_id = col_character(),
  img_width = col_integer(),
  img_height = col_integer()
)
face_details %>%
  ggplot(aes(x = img_width, y = img_height)) +
  geom_point( alpha = 1 / 10) +
  stat_density2d(aes(fill = ..level..), geom="polygon")

face_details_by_artist <- face_details %>% group_by(cat_id, artist_id) %>% mutate(face_count = n()) %>% ungroup()
face_details_by_artist %>% distinct(cat_id, artist_id, .keep_all = TRUE) %>%
  ggplot(aes(x = as.factor(face_count))) + 
  geom_bar() + 
  labs(title = "Counts of Artists by Face Count")

face_details_by_artist %>% 
  ggplot(aes(x = as.factor(face_count), y = img_width)) + 
  geom_boxplot() + 
  labs(title = "Face Width by Face Count")

face_details_by_artist %>% 
  ggplot(aes(x = as.factor(face_count), y = img_height)) + 
  geom_boxplot() + 
  labs(title = "Face Height by Face Count")

face_details_by_artist %>% 
  ggplot(aes(x = as.factor(face_count), y = img_height)) + 
  geom_boxplot() + 
  facet_wrap(~ cat_id) +
  labs(title = "Face Height by Face Count")

LS0tCnRpdGxlOiAiQmFuZCBHYXplIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgpgYGB7cn0KbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkoZ2d0aGVtZXMpCmxpYnJhcnkoc2NhbGVzKQpsaWJyYXJ5KGZvcmNhdHMpCmBgYAoKYGBge3J9CmxpYnJhcnkoanBlZykKbGlicmFyeShrbml0cikKYGBgCgoKYGBge3J9CmZpbGVuYW1lIDwtICdkYXRhL2ZhY2VzLmNzdicKZmFjZXMgPC0gcmVhZF9jc3YoZmlsZW5hbWUpCgpgYGAKCmBgYHtyfQpmYWNlcyA8LSBmYWNlcyAlPiUgbXV0YXRlKGhhc19mYWNlID0gZmFjZV9jb3VudCA+IDApCndpdGhfZmFjZXMgPC0gZmFjZXMgJT4lIGZpbHRlcihmYWNlX2NvdW50ID4gMCkKYGBgCgpgYGB7cn0KdW5pcXVlX2FydGlzdHMgPC0gZmFjZXMgJT4lIGRpc3RpbmN0KGNhdF9pZCwgYXJ0aXN0X2lkLCAua2VlcF9hbGwgPSBUUlVFKQpgYGAKCgojIyBBcnRpc3QgQ291bnRzCgoKYGBge3J9CnVuaXF1ZV9hcnRpc3RzICU+JQogIGdncGxvdChhZXMoeCA9IGZjdF9yZXYoZmN0X2luZnJlcShjYXRfbmFtZSkpKSkgKwogIGNvb3JkX2ZsaXAoKSArCiAgZ2VvbV9iYXIoKSArCiAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IGNvbW1hKSArCiAgbGFicyh0aXRsZSA9ICJBcnRpc3RzIFBlciBDYXRlZ29yeSIsIHkgPSAiIikgKwogIHRoZW1lX2ZpdmV0aGlydHllaWdodCgpCmBgYAoKYGBge3J9CmJ5Q2F0IDwtIHVuaXF1ZV9hcnRpc3RzICU+JSBncm91cF9ieShjYXRfbmFtZSkgJT4lIG11dGF0ZShjYXRfY291bnQgPSBuKCkpICU+JQogIGFycmFuZ2UoLWNhdF9jb3VudCkgJT4lCiAgZ3JvdXBfYnkoY2F0X25hbWUsIGhhc19mYWNlKSAlPiUgc3VtbWFyaXNlKGNvdW50ID0gbigpLCBwZXIgPSBjb3VudCAvIGNhdF9jb3VudFsxXSwgY2F0X2NvdW50ID0gY2F0X2NvdW50WzFdKSAlPiUgYXJyYW5nZShjYXRfY291bnQpCmBgYAoKYGBge3J9CmJ5Q2F0ICU+JSBmaWx0ZXIoaGFzX2ZhY2UgPT0gVFJVRSkgJT4lCiAgZ2dwbG90KGFlcyh4ID0gZmN0X2lub3JkZXIoY2F0X25hbWUpLCB5ID0gY291bnQgKSkgKyAKICBjb29yZF9mbGlwKCkgKwogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiKSArCiAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IGNvbW1hKSArCiAgbGFicyh0aXRsZSA9ICJDb3VudCBvZiBBcnRpc3RzIHdpdGggRmFjZXMiLCB5ID0gIiIpICsKICB0aGVtZV9maXZldGhpcnR5ZWlnaHQoKQoKYGBgCgpgYGB7cn0KYnlDYXQgJT4lIAogIGdncGxvdChhZXMoeCA9IGZjdF9pbm9yZGVyKGNhdF9uYW1lKSwgeSA9IHBlciwgZmlsbCA9IGhhc19mYWNlKSkgKyAKICBjb29yZF9mbGlwKCkgKwogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBwZXJjZW50KSArCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIpICsgCiAgbGFicyh4ID0gIiIsIHkgPSAiIiwgdGl0bGUgPSAiUGVyY2VudCBBcnRpc3RzIHdpdGggRmFjZSBieSBDYXRlZ29yeSIpICsKICB0aGVtZV9maXZldGhpcnR5ZWlnaHQoKQoKYGBgCgoKIyMgRmFjZSBDb3VudHMKCmBgYHtyfQpmYWNlcyAlPiUKICBnZ3Bsb3QoYWVzKHggPSAoZmFjZV9jb3VudCkpKSArCiAgZ2VvbV9oaXN0b2dyYW0oYmlud2lkdGggPSAxLjApICsKICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gY29tbWEpICsKICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzID0gc2VxKDAsIDE1LCAxKSkgKwogIHRoZW1lX2ZpdmV0aGlydHllaWdodCgpICsKICBsYWJzKHRpdGxlID0gIkRpc3RyaWJ1dGlvbiBvZiBGYWNlIENvdW50IiwgeCA9ICJOdW1iZXIgb2YgRmFjZXMgaW4gQXJ0aXN0IEltYWdlIikKCmBgYAoKYGBge3J9CnVuaXF1ZV9hcnRpc3RzICU+JQogIGdncGxvdChhZXMoeCA9IChmYWNlX2NvdW50KSkpICsKICBnZW9tX2hpc3RvZ3JhbShiaW53aWR0aCA9IDEuMCkgKwogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBjb21tYSkgKwogIHNjYWxlX3hfY29udGludW91cyhicmVha3MgPSBzZXEoMCwgMTUsIDEpKSArCiAgdGhlbWVfZml2ZXRoaXJ0eWVpZ2h0KCkgKwogIGxhYnModGl0bGUgPSAiRGlzdHJpYnV0aW9uIG9mIEZhY2UgQ291bnQgZm9yIEFydGlzdHMiLCB4ID0gIk51bWJlciBvZiBGYWNlcyBpbiBBcnRpc3QgSW1hZ2UiKQoKYGBgCgpMb3RzIG9mIG5vLWZhY2VzIGRldGVjdGVkIGFuZCBzb2xvIGFydGlzdHMuCgpgYGB7cn0KbWF4KHVuaXF1ZV9hcnRpc3RzJGZhY2VfY291bnQpCmBgYAoKCgojIyMgRmFjZSBDb3VudCBieSBHZW5yZQoKYGBge3J9CmJ5Q2F0RmFjZUNvdW50IDwtIHVuaXF1ZV9hcnRpc3RzICU+JSBmaWx0ZXIoaGFzX2ZhY2UgPT0gVFJVRSkgJT4lCiAgZ3JvdXBfYnkoY2F0X25hbWUpICU+JSBzdW1tYXJpc2UobWVhbl9mYWNlX2NvdW50ID0gbWVhbihmYWNlX2NvdW50KSwgbWVkaWFuX2ZhY2VfY291bnQgPSBtZWRpYW4oZmFjZV9jb3VudCkpCiAgCmBgYAoKYGBge3J9CgpgYGAKCgpgYGB7cn0KdW5pcXVlX2FydGlzdHMgJT4lIGZpbHRlcihoYXNfZmFjZSA9PSBUUlVFKSAlPiUgI2ZpbHRlcihjYXRfaWQgPT0gIm1ldGFsIikgJT4lCiAgZ2dwbG90KGFlcyh4ID0gZmFjZV9jb3VudCkpICsKICAjZ2VvbV9oaXN0b2dyYW0oYmlud2lkdGggPSAxLjApICsKICAjZ2VvbV9oaXN0b2dyYW0oYWVzKHk9Li5jb3VudC4uL3N1bSguLmNvdW50Li4pKSwgYmlud2lkdGggPSAxLjApICsgCiAgZ2VvbV9oaXN0b2dyYW0oYWVzKHkgPSAoLi5jb3VudC4uKS90YXBwbHkoLi5jb3VudC4uLC4uUEFORUwuLixzdW0pWy4uUEFORUwuLl0pLCBiaW53aWR0aCA9IDEuMCkgKyAKICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gcGVyY2VudCkgKwogIHNjYWxlX3hfY29udGludW91cyhicmVha3MgPSBzZXEoMSwgMTQsIDIpKSArCiAgZmFjZXRfd3JhcCh+IGNhdF9uYW1lKSArIAogIGxhYnModGl0bGUgPSAiRGlzdHJpYnV0aW9uIG9mIEZhY2UgQ291bnQgZm9yIEFydGlzdHMgYnkgQ2F0ZWdvcnkiLCB4ID0gIk51bWJlciBvZiBGYWNlcyBpbiBBcnRpc3QgSW1hZ2UiKSArCiAgdGhlbWVfZml2ZXRoaXJ0eWVpZ2h0KCkKYGBgCgpDb29sIQoKU28gQ291bnRyeSBhbmQgSGlwIEhvcCBhcnRpc3RzIGFsbW9zdCBhbHdheXMgaGF2ZSBzb2xvIGltYWdlcy4KCkJ1dCB5b3UgY2FuJ3QgaGF2ZSBhIG1ldGFsLCBwdW5rLCBvciByb2NrIGJhbmQgd2l0aG91dCBhdCBsZWFzdCBhIGZldyBwZW9wbGUuIAoKCiMjIEdlbmRlcgoKYGBge3J9CndpdGhfZmFjZXMgJT4lCiAgZ2dwbG90KGFlcyh4ID0gMSwgZmlsbCA9IGZhY2VfZ2VuZGVyKSkgKyAKICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gY29tbWEpICsKICBzY2FsZV94X2NvbnRpbnVvdXMobGFiZWxzID0gYygpKSArCiAgZ2VvbV9iYXIoKSArIAogIGxhYnMoeCA9ICIiLCB5ID0gIiIsIHRpdGxlID0gIkNvdW50IG9mIEZhY2VzIGJ5IEdlbmRlciIpICsKICB0aGVtZV9maXZldGhpcnR5ZWlnaHQoKQpgYGAKCgpgYGB7cn0Kd2l0aF9mYWNlcyAlPiUKICBnZ3Bsb3QoYWVzKHggPSAxLCBmaWxsID0gZmFjZV9nZW5kZXIpKSArIAogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBwZXJjZW50KSArCiAgc2NhbGVfeF9jb250aW51b3VzKGxhYmVscyA9IGMoKSkgKwogIGdlb21fYmFyKGFlcyh5ID0gKC4uY291bnQuLikvc3VtKC4uY291bnQuLikpLCBwb3NpdGlvbiA9ICJkb2RnZSIpICsgCiAgbGFicyh4ID0gIiIsIHkgPSAiIiwgdGl0bGUgPSAiUGVyY2VudCBvZiBGYWNlcyBieSBHZW5kZXIiKSArCiAgdGhlbWVfZml2ZXRoaXJ0eWVpZ2h0KCkKYGBgCgpMb3RzIG9mIER1ZGVzIGluIHRoaXMgZGF0YSEKCiMjIyBHZW5kZXIgYnkgQ2F0ZWdvcnkKCgpgYGB7cn0Kd2l0aF9mYWNlcyAlPiUgI2ZpbHRlcihmYWNlX2dlbmRlciA9PSAnZmVtYWxlJykgJT4lIAogIGdncGxvdChhZXMoeCA9IGZhY2VfZ2VuZGVyKSkgICsKICBnZW9tX2JhcigpICsKICAjZ2VvbV9oaXN0b2dyYW0oYWVzKHk9Li5jb3VudC4uL3N1bSguLmNvdW50Li4pKSwgYmlud2lkdGggPSAxLjApICsgCiAgI2dlb21faGlzdG9ncmFtKGFlcyh5ID0gKC4uY291bnQuLikvdGFwcGx5KC4uY291bnQuLiwuLlBBTkVMLi4sc3VtKVsuLlBBTkVMLi5dKSwgYmlud2lkdGggPSAxLjApICsgCiAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IGNvbW1hKSArCiAgI3NjYWxlX3hfY29udGludW91cyhicmVha3MgPSBzZXEoMSwgMTQsIDIpKSArCiAgZmFjZXRfd3JhcCh+IGNhdF9uYW1lKSArIAogIGxhYnModGl0bGUgPSAiR2VuZGVyIENvdW50cyBvZiBGYWNlcyBieSBDYXRlZ29yeSIsIHggPSAiIikgKwogIHRoZW1lX2ZpdmV0aGlydHllaWdodCgpCmBgYAoKYGBge3J9CndpdGhfZmFjZXMgJT4lICNmaWx0ZXIoZmFjZV9nZW5kZXIgPT0gJ2ZlbWFsZScpICU+JSAKICBnZ3Bsb3QoYWVzKHggPSBmYWNlX2dlbmRlcikpICArCiAgI2dlb21fYmFyKCkgKwogICNnZW9tX2hpc3RvZ3JhbShhZXMoeT0uLmNvdW50Li4vc3VtKC4uY291bnQuLikpLCBiaW53aWR0aCA9IDEuMCkgKyAKICBnZW9tX2JhcihhZXMoeSA9ICguLmNvdW50Li4pL3RhcHBseSguLmNvdW50Li4sLi5QQU5FTC4uLHN1bSlbLi5QQU5FTC4uXSkgKSArIAogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBwZXJjZW50KSArCiAgI3NjYWxlX3hfY29udGludW91cyhicmVha3MgPSBzZXEoMSwgMTQsIDIpKSArCiAgZmFjZXRfd3JhcCh+IGNhdF9uYW1lKSArIAogIGxhYnModGl0bGUgPSAiR2VuZGVyIFBlcmNlbnRzIG9mIEZhY2VzIGJ5IENhdGVnb3J5IiwgeCA9ICIiKSArCiAgdGhlbWVfZml2ZXRoaXJ0eWVpZ2h0KCkKYGBgCgoKIyMgR2xhc3NlcwoKYGBge3J9CndpdGhfZmFjZXMgJT4lCiAgZ2dwbG90KGFlcyh4ID0gZmFjZV9nbGFzc2VzLCBmaWxsID0gZmFjZV9nbGFzc2VzKSkgKyAKICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gcGVyY2VudCkgKwogIGdlb21fYmFyKGFlcyh5ID0gKC4uY291bnQuLikvc3VtKC4uY291bnQuLikpLCBwb3NpdGlvbiA9ICJkb2RnZSIpICsgCiAgbGFicyh4ID0gIiIsIHkgPSAiIiwgdGl0bGUgPSAiUGVyY2VudGFnZSBvZiBHbGFzc2VzIG9uIEZhY2VzIiwgZmlsbCA9ICJHbGFzc2VzIFR5cGUiKSArCiAgdGhlbWVfZml2ZXRoaXJ0eWVpZ2h0KCkKYGBgCgpTd2ltbWluZyBHb2dnbGVzID8/IFRoYXQgY2FuJ3QgYmUgcmlnaHQuCgpgYGB7cn0KZmFjZV9pbmQgPC0gYygxLDIsMykKZmlsdGVyZWRfZmFjZXMgPC0gd2l0aF9mYWNlcyAlPiUgZmlsdGVyKGZhY2VfZ2xhc3NlcyA9PSAnU3dpbW1pbmdHb2dnbGVzJykgJT4lIGZpbHRlcihmYWNlX2NvdW50ID09IDEpIApyb3cgPC0gZmlsdGVyZWRfZmFjZXNbZmFjZV9pbmQsXQpmaWxlbmFtZSA8LSBwYXN0ZSgiZGF0YS9pbWdzLyIsIHJvdyRjYXRfaWQsICIvIiwgcm93JGFydGlzdF9pZCwgIi5qcGciLCBzZXA9IiIpCmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKGZpbGVuYW1lKQpgYGAKCgpXZWxsLCBvay4gTWF5YmUgbm90LgoKYGBge3J9CndpdGhfZmFjZXMgJT4lIGZpbHRlcighKGZhY2VfZ2xhc3NlcyA9PSAnTm9HbGFzc2VzJykpICU+JSBmaWx0ZXIoIShmYWNlX2dsYXNzZXMgPT0gJ1N3aW1taW5nR29nZ2xlcycpKSAlPiUKICBnZ3Bsb3QoYWVzKHggPSBmYWNlX2dsYXNzZXMsIGZpbGwgPSBmYWNlX2dsYXNzZXMpKSArIAogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBwZXJjZW50KSArCiAgZ2VvbV9iYXIoYWVzKHkgPSAoLi5jb3VudC4uKS90YXBwbHkoLi5jb3VudC4uLC4uUEFORUwuLixzdW0pWy4uUEFORUwuLl0pICkgKyAKICBmYWNldF93cmFwKH4gY2F0X25hbWUpICsgCiAgbGFicyh4ID0gIiIsIHkgPSAiIiwgdGl0bGUgPSAiU3VuIEdsYXNzZXMgdnMgUmVhZGluZyBHbGFzc2VzIGJ5IENhdGVnb3J5IiwgZmlsbCA9ICJHbGFzc2VzIFR5cGUiKSArCiAgdGhlbWVfZml2ZXRoaXJ0eWVpZ2h0KCkKYGBgCgojIyAzRCBGYWNlIFBvc2UKClRoZXJlIGFyZSAzIHZhbHVlcyByZWNvcmRlZCBmb3IgZmFjZSBwb3NlOiBZYXcsIFJvbGwsIGFuZCBQaXRjaC4gCgpNb3N0IGV4cGxhaW5hdGlvbnMgb2YgdGhlc2UgZmVhdHVyZXMgb25seSBleHBsYWluIGl0IGZvciBbQWlyY3JhZnRdKGh0dHBzOi8vd3d3LnlvdXR1YmUuY29tL3dhdGNoP3Y9cFEyNE50bmFMbDgpLCBidXQgaGVyZSBpcyBteSB1bmRlcnN0YW5kaW5nIGZvciBodW1hbiBmYWNlczoKCiogWWF3OiBUaGUgZGlyZWN0aW9uIHlvdXIgbm9zZSBpcyBwb2ludGluZywgdG8gdGhlIGxlZnQgb3IgcmlnaHQuCiogUm9sbDogQ29ja2luZyB5b3VyIGhlYWQgdG8gdGhlIGxlZnQgb3IgcmlnaHQuCiogUGl0Y2g6IExvb2tpbmcgdXAgb3IgZG93bi4KClVuZm9ydHVuYXRlbHksIFBpdGNoIGlzbid0IHlldCBjb21wdXRlZCAtIHNvIHdlIGNhbiBvbmx5IGxvb2sgCgpMZXRzIGxvb2sgYXQgWWF3IGFuZCBSb2xsOgoKIyMjIFlhdwoKYGBge3J9CndpdGhfZmFjZXMgJT4lCiAgZ2dwbG90KGFlcyh4ID0gZmFjZV95YXcpKSArCiAgZ2VvbV9oaXN0b2dyYW0oYmlud2lkdGggPSAyKSArCiAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IGNvbW1hKSArCiAgI3NjYWxlX3hfY29udGludW91cyhicmVha3MgPSBzZXEoMCwgMTUsIDEpKSArCiAgdGhlbWVfZml2ZXRoaXJ0eWVpZ2h0KCkgKwogIGxhYnModGl0bGUgPSAiRGlzdHJpYnV0aW9uIG9mIFlhdyBNZXRyaWMiLCB4ID0gIiIpCmBgYAoKTWVhbjoKCmBgYHtyfQptZWFuKHdpdGhfZmFjZXMkZmFjZV95YXcpCmBgYAoKTWVkaWFuOgoKYGBge3J9Cm1lZGlhbih3aXRoX2ZhY2VzJGZhY2VfeWF3KQpgYGAKClNvIHRoZXJlIGlzIGEgc2xpZ2h0IHRyZW5kIG9mIHRoZSBub3NlIHBvaW50aW5nIHRvIHRoZSBsZWZ0IC4uLiBvciBhdCBsZWFzdCB0aGF0IGlzIHdoYXQgdGhlIGFsZ29yaXRobSBpcyBkZXRlY3RpbmcuIENvdWxkIGJlIGEgYmlhcyEKCkJ1dCByZWFsbHksIGFzIHdlIHNlZSBiZWxvdywgLTEgb3IgLTIgaXMgcmVhbGx5IG5vdCB2aXNpYmx5IG5vdGljYWJsZSBhcyBhIGhlYWQgdHVybi4KCgojIyMgWWF3IEV4YW1wbGVzCgoqKkxpZ2h0IE5lZ2F0aXZlIFlhdyoqCgpGYWNlcyB3aXRoIHlhdyBiZXR3ZWVuIC0xIGFuZCAtMS44CgpgYGB7cn0KZmFjZV9pbmQgPC0gYygxLDIsMykKZmlsdGVyZWRfZmFjZXMgPC0gd2l0aF9mYWNlcyAlPiUgZmlsdGVyKGZhY2VfY291bnQgPT0gMSkgJT4lIGZpbHRlcihmYWNlX3lhdyA8IC0xKSAlPiUgZmlsdGVyKGZhY2VfeWF3ID4gLTEuOCkKcm93IDwtIGZpbHRlcmVkX2ZhY2VzW2ZhY2VfaW5kLF0KZmlsZW5hbWUgPC0gcGFzdGUoImRhdGEvaW1ncy8iLCByb3ckY2F0X2lkLCAiLyIsIHJvdyRhcnRpc3RfaWQsICIuanBnIiwgc2VwPSIiKQprbml0cjo6aW5jbHVkZV9ncmFwaGljcyhmaWxlbmFtZSkKYGBgCgoqKk1lZGl1bSBOZWdhdGl2ZSBZYXcqKgoKRmFjZXMgd2l0aCBZYXcgYmV0d2VlbiAtMTAgYW5kIC0xNQoKYGBge3J9Cm1lZF9uZWdfeWF3IDwtIHdpdGhfZmFjZXMgJT4lIGZpbHRlcihmYWNlX2NvdW50ID09IDEpICU+JSBmaWx0ZXIoZmFjZV95YXcgPCAtMTApICU+JSBmaWx0ZXIoZmFjZV95YXcgPiAtMTUpCnJvdyA8LSBtZWRfbmVnX3lhd1tmYWNlX2luZCxdCmZpbGVuYW1lIDwtIHBhc3RlKCJkYXRhL2ltZ3MvIiwgcm93JGNhdF9pZCwgIi8iLCByb3ckYXJ0aXN0X2lkLCAiLmpwZyIsIHNlcD0iIikKa25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MoZmlsZW5hbWUpCmBgYAoKKipNZWRpdW0gUG9zaXRpdmUgWWF3KioKCkZhY2VzIHdpdGggWWF3IGJldHdlZW4gMTAgYW5kIDE1CgpgYGB7cn0KbWVkX3Bvc195YXcgPC0gd2l0aF9mYWNlcyAlPiUgZmlsdGVyKGZhY2VfY291bnQgPT0gMSkgJT4lIGZpbHRlcihmYWNlX3lhdyA+IDEwKSAlPiUgZmlsdGVyKGZhY2VfeWF3IDwgMTUpCnJvdyA8LSBtZWRfcG9zX3lhd1tmYWNlX2luZCxdCmZpbGVuYW1lIDwtIHBhc3RlKCJkYXRhL2ltZ3MvIiwgcm93JGNhdF9pZCwgIi8iLCByb3ckYXJ0aXN0X2lkLCAiLmpwZyIsIHNlcD0iIikKa25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MoZmlsZW5hbWUpCmBgYAoKKipIZWF2eSBQb3NpdGl2ZSBZYXcqKgoKRmFjZXMgd2l0aCA+IDMwIFlhdwoKYGBge3J9CmZpbHRlcmVkX2ZhY2VzIDwtIHdpdGhfZmFjZXMgJT4lIGZpbHRlcihmYWNlX2NvdW50ID09IDEpICU+JSBmaWx0ZXIoZmFjZV95YXcgPiAzMCkKcm93IDwtIGZpbHRlcmVkX2ZhY2VzW2ZhY2VfaW5kLF0KZmlsZW5hbWUgPC0gcGFzdGUoImRhdGEvaW1ncy8iLCByb3ckY2F0X2lkLCAiLyIsIHJvdyRhcnRpc3RfaWQsICIuanBnIiwgc2VwPSIiKQprbml0cjo6aW5jbHVkZV9ncmFwaGljcyhmaWxlbmFtZSkKYGBgCgoKIyMjIFJvbGwKCkxldCdzIGdldCBvdXIgUm9sbCBvbi4KCmBgYHtyfQp3aXRoX2ZhY2VzICU+JQogIGdncGxvdChhZXMoeCA9IGZhY2Vfcm9sbCkpICsKICBnZW9tX2hpc3RvZ3JhbShiaW53aWR0aCA9IDIpICsKICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gY29tbWEpICsKICAjc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcyA9IHNlcSgwLCAxNSwgMSkpICsKICB0aGVtZV9maXZldGhpcnR5ZWlnaHQoKSArCiAgbGFicyh0aXRsZSA9ICJEaXN0cmlidXRpb24gb2YgUm9sbCBNZXRyaWMiLCB4ID0gIiIpCmBgYAoKCmBgYHtyfQptZWFuKHdpdGhfZmFjZXMkZmFjZV9yb2xsKQpgYGAKCmBgYHtyfQptZWRpYW4od2l0aF9mYWNlcyRmYWNlX3JvbGwpCmBgYAoKUm9sbCBsb29rcyB0byBiZSBkaXN0cmlidXRlZCBwcmV0dHkgZXZlbmx5IC0gYnV0IHRoZXJlIGlzIGEgbG9uZ2VyIHRhaWwgdG8gdGhlIHJpZ2h0LiBUaGUgc3VtbWFyeSBzdGF0cyBpbmRpY2F0ZSB0aGVyZSBpcyBhIHNsaWdodCBkcmFnIHRvIHRoZSBsZWZ0LiAgIAoKTGV0cyBsb29rIGF0IHNvbWUgZXhhbXBsZXMhCgojIyMgUm9sbCBFeGFtcGxlcwoKKipMaWdodCBOZWdhdGl2ZSBSb2xsKioKCkZhY2VzIHdpdGggcm9sbCBiZXR3ZWVuIC0xIGFuZCAtMgoKYGBge3J9CmZhY2VfaW5kIDwtIGMoMSwyLDMpCmZpbHRlcmVkX2ZhY2VzIDwtIHdpdGhfZmFjZXMgJT4lIGZpbHRlcihmYWNlX2NvdW50ID09IDEpICU+JSBmaWx0ZXIoZmFjZV9yb2xsIDwgLTEpICU+JSBmaWx0ZXIoZmFjZV9yb2xsID4gLTIpCnJvdyA8LSBmaWx0ZXJlZF9mYWNlc1tmYWNlX2luZCxdCmZpbGVuYW1lIDwtIHBhc3RlKCJkYXRhL2ltZ3MvIiwgcm93JGNhdF9pZCwgIi8iLCByb3ckYXJ0aXN0X2lkLCAiLmpwZyIsIHNlcD0iIikKa25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MoZmlsZW5hbWUpCmBgYAoKKipIZWF2eSBOZWdhdGl2ZSBSb2xsKioKCkZhY2VzIHdpdGggcm9sbCA8IC0yMAoKYGBge3J9CmZpbHRlcmVkX2ZhY2VzIDwtIHdpdGhfZmFjZXMgJT4lIGZpbHRlcihmYWNlX2NvdW50ID09IDEpICU+JSBmaWx0ZXIoZmFjZV9yb2xsIDwgLTIwKQpyb3cgPC0gZmlsdGVyZWRfZmFjZXNbZmFjZV9pbmQsXQpmaWxlbmFtZSA8LSBwYXN0ZSgiZGF0YS9pbWdzLyIsIHJvdyRjYXRfaWQsICIvIiwgcm93JGFydGlzdF9pZCwgIi5qcGciLCBzZXA9IiIpCmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKGZpbGVuYW1lKQpgYGAKCioqSGVhdnkgUG9zaXRpdmUgUm9sbCoqCgpGYWNlcyB3aXRoIHJvbGwgPiAyMAoKYGBge3J9CmZpbHRlcmVkX2ZhY2VzIDwtIHdpdGhfZmFjZXMgJT4lIGZpbHRlcihmYWNlX2NvdW50ID09IDEpICU+JSBmaWx0ZXIoZmFjZV9yb2xsID4gMjApCnJvdyA8LSBmaWx0ZXJlZF9mYWNlc1tmYWNlX2luZCxdCmZpbGVuYW1lIDwtIHBhc3RlKCJkYXRhL2ltZ3MvIiwgcm93JGNhdF9pZCwgIi8iLCByb3ckYXJ0aXN0X2lkLCAiLmpwZyIsIHNlcD0iIikKa25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MoZmlsZW5hbWUpCmBgYAoKCiMjIEZhY2lhbCBIYWlyCgpMb29rIGp1c3QgYXQgQmVhcmRzIGZvciBhIHNlY29uZDoKCmBgYHtyfQp3aXRoX2ZhY2VzICU+JSBmaWx0ZXIoZmFjZV9nZW5kZXIgPT0gJ21hbGUnKSAlPiUKICBnZ3Bsb3QoYWVzKHggPSBmYWNlX2JlYXJkKSkgKyAKICBnZW9tX2ZyZXFwb2x5KGJpbndpZHRoID0gMC4wMSkgKyAKICBsYWJzKHRpdGxlID0gIk1hbGUgQmVhcmQgTGlrZWxpaG9vZCBWYWx1ZXMiKQpgYGAKCkludGVyZXN0aW5nISBTbyB0aGlzIGlzbid0IHJlYWxseSBhIGNvbnRpbnVvdXMgdmFyaWFibGUsIGluc3RlYWQgaXRzIGRlc2NyaXRpemVkLiAKClRvIGRvdWJsZSBjaGVjayB0aGF0LCBoZXJlIGFyZSB0aGUgdW5pcXVlIHZhbHVlcyBvZiBgZmFjZV9iZWFyZGA6CgpgYGB7cn0Kc29ydCh1bmlxdWUod2l0aF9mYWNlcyRmYWNlX2JlYXJkKSkKYGBgCgoKCmBgYHtyfQpmYWNpYWxfaGFpciA8LSB3aXRoX2ZhY2VzICU+JSB0aWR5cjo6Z2F0aGVyKGtleSA9IGhhaXJfdHlwZSwgdmFsdWUgPSBoYWlyX3JhdGlvLCBmYWNlX2JlYXJkLCBmYWNlX21vdXN0YWNoZSwgZmFjZV9zaWRlYnVybnMpCmBgYAoKCmBgYHtyfQpmYWNpYWxfaGFpciAlPiUgZmlsdGVyKGZhY2VfZ2VuZGVyID09ICdtYWxlJykgJT4lCiAgZ2dwbG90KGFlcyh4ID0gYXMuZmFjdG9yKGhhaXJfcmF0aW8pLCBmaWxsID0gaGFpcl90eXBlKSkgKyAKICBnZW9tX2Jhcihwb3NpdGlvbiA9ICJkb2RnZSIpICsgCiAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IGNvbW1hKSArCiAgbGFicyh0aXRsZSA9ICJNYWxlIEZhY2lhbCBIYWlyIENvdW50cyIsIGZpbGwgPSAiSGFpciBUeXBlIikgKyAKICB0aGVtZV9maXZldGhpcnR5ZWlnaHQoKQpgYGAKCmBgYHtyfQpmYWNpYWxfaGFpciAlPiUgZmlsdGVyKGZhY2VfZ2VuZGVyID09ICdtYWxlJykgJT4lCiAgZ2dwbG90KGFlcyh4ID0gYXMuZmFjdG9yKGhhaXJfcmF0aW8pLCBmaWxsID0gaGFpcl90eXBlKSkgKyAKICAjZ2VvbV9iYXIocG9zaXRpb24gPSAiZG9kZ2UiKSArIAogIGdlb21fYmFyKGFlcyh5PS4uY291bnQuLi9zdW0oLi5jb3VudC4uKSksIHBvc2l0aW9uID0gImRvZGdlIikgKyAKICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gcGVyY2VudCkgKwogIGxhYnModGl0bGUgPSAiTWFsZSBGYWNpYWwgSGFpciBQZXJjZW50cyIsIGZpbGwgPSAiSGFpciBUeXBlIikgKyAKICB0aGVtZV9maXZldGhpcnR5ZWlnaHQoKQpgYGAKCmBgYHtyfQoKZmFjaWFsX2hhaXIgJT4lIGZpbHRlcihmYWNlX2dlbmRlciA9PSAnbWFsZScpICU+JQogIGdncGxvdChhZXMoeCA9IGFzLmZhY3RvcihoYWlyX3JhdGlvKSwgZmlsbCA9IGhhaXJfdHlwZSkpICsgCiAgI2dlb21fYmFyKHBvc2l0aW9uID0gImRvZGdlIikgKyAKICBnZW9tX2JhcihhZXMoeT0uLmNvdW50Li4vc3VtKC4uY291bnQuLikpLCBwb3NpdGlvbiA9ICJkb2RnZSIpICsgCiAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IHBlcmNlbnQpICsKICBsYWJzKHRpdGxlID0gIk1hbGUgRmFjaWFsIEhhaXIgUGVyY2VudHMiLCBmaWxsID0gIkhhaXIgVHlwZSIpICsgCiAgZmFjZXRfd3JhcCh+IGhhaXJfdHlwZSkgKwogIHRoZW1lX2ZpdmV0aGlydHllaWdodCgpCmBgYAoKCldoYXQgYWJvdXQgYGZlbWFsZWAgaWRlbnRpZmllZCBmYWNlcz8KCmBgYHtyfQpmYWNpYWxfaGFpciAlPiUgZmlsdGVyKGZhY2VfZ2VuZGVyID09ICdmZW1hbGUnKSAlPiUKICBnZ3Bsb3QoYWVzKHggPSBoYWlyX3JhdGlvLCBmaWxsID0gaGFpcl90eXBlKSkgKyAKICBnZW9tX2hpc3RvZ3JhbShiaW53aWR0aCA9IDAuMDEpICsKICBsYWJzKHRpdGxlID0gIldvbWVuIGRvbid0IGhhdmUgRmFjaWFsIEhhaXIgaW4gdGhpcyBEYXRhc2V0IC8gQWxnb3JpdGhtIikKYGBgCgojIyMgRmFjaWFsIEhhaXIgYnkgQ2F0ZWdvcnkKCgoKCgpgYGB7cn0KCmZhY2lhbF9oYWlyICU+JSBmaWx0ZXIoZmFjZV9nZW5kZXIgPT0gJ21hbGUnKSAlPiUgZmlsdGVyKGhhaXJfdHlwZSA9PSAnZmFjZV9iZWFyZCcpICU+JQogIGdncGxvdChhZXMoeCA9IGFzLmZhY3RvcihoYWlyX3JhdGlvKSApKSArIAogICNnZW9tX2Jhcihwb3NpdGlvbiA9ICJkb2RnZSIpICsgCiAgI2dlb21fYmFyKGFlcyh5PS4uY291bnQuLi9zdW0oLi5jb3VudC4uKSkpICsgCiAgZ2VvbV9iYXIoYWVzKHkgPSAoLi5jb3VudC4uKS90YXBwbHkoLi5jb3VudC4uLC4uUEFORUwuLixzdW0pWy4uUEFORUwuLl0pKSArIAogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBwZXJjZW50KSArCiAgbGFicyh0aXRsZSA9ICJCZWFyZHMgYnkgQ2F0ZWdvcnkiKSArIAogIGZhY2V0X3dyYXAofiBjYXRfbmFtZSkgKwogIHRoZW1lX2ZpdmV0aGlydHllaWdodCgpCmBgYAoKYGBge3J9CmZpbHRlcmVkX2ZhY2VzIDwtIHdpdGhfZmFjZXMgJT4lIGZpbHRlcihmYWNlX2NvdW50ID09IDEpICU+JSBmaWx0ZXIoZmFjZV9nZW5kZXIgPT0gJ21hbGUnKSAlPiUgZmlsdGVyKGZhY2VfYmVhcmQgPT0gMS4wKSAKCnJvdyA8LSBmaWx0ZXJlZF9mYWNlc1tmYWNlX2luZCxdCmZpbGVuYW1lIDwtIHBhc3RlKCJkYXRhL2ltZ3MvIiwgcm93JGNhdF9pZCwgIi8iLCByb3ckYXJ0aXN0X2lkLCAiLmpwZyIsIHNlcD0iIikKa25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MoZmlsZW5hbWUpCmBgYAoKCmBgYHtyfQpmaWx0ZXJlZF9mYWNlcyA8LSB3aXRoX2ZhY2VzICU+JSBmaWx0ZXIoZmFjZV9jb3VudCA9PSAxKSAlPiUgZmlsdGVyKGZhY2VfZ2VuZGVyID09ICdtYWxlJykgJT4lIGZpbHRlcihmYWNlX2JlYXJkID09IDEuMCkgCgpyb3cgPC0gZmlsdGVyZWRfZmFjZXNbZmFjZV9pbmQsXQpmaWxlbmFtZSA8LSBwYXN0ZSgiZGF0YS9pbWdzLyIsIHJvdyRjYXRfaWQsICIvIiwgcm93JGFydGlzdF9pZCwgIi5qcGciLCBzZXA9IiIpCmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKGZpbGVuYW1lKQpgYGAKCmBgYHtyfQptYWxlX2hhaXIgPC0gd2l0aF9mYWNlcyAlPiUgZmlsdGVyKGZhY2VfZ2VuZGVyID09ICdtYWxlJykgJT4lIG11dGF0ZShoYXNfZmFjaWFsX2hhaXIgPSBmYWNlX2JlYXJkICsgZmFjZV9tb3VzdGFjaGUgPiAwLjQpCmBgYAoKCmBgYHtyfQptYWxlX2hhaXIgJT4lCiAgZ2dwbG90KGFlcyh4ID0gaGFzX2ZhY2lhbF9oYWlyLCBmaWxsID0gaGFzX2ZhY2lhbF9oYWlyKSkgICsKICAjZ2VvbV9iYXIoKSArCiAgI2dlb21faGlzdG9ncmFtKGFlcyh5PS4uY291bnQuLi9zdW0oLi5jb3VudC4uKSksIGJpbndpZHRoID0gMS4wKSArIAogIGdlb21fYmFyKGFlcyh5ID0gKC4uY291bnQuLikvdGFwcGx5KC4uY291bnQuLiwuLlBBTkVMLi4sc3VtKVsuLlBBTkVMLi5dKSApICsgCiAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IHBlcmNlbnQpICsKICAjc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcyA9IHNlcSgxLCAxNCwgMikpICsKICBmYWNldF93cmFwKH4gY2F0X25hbWUpICsgCiAgbGFicyh0aXRsZSA9ICJHZW5kZXIgUGVyY2VudHMgb2YgRmFjZXMgYnkgQ2F0ZWdvcnkiLCB4ID0gIiIpICsKICB0aGVtZV9maXZldGhpcnR5ZWlnaHQoKQpgYGAKCiMjIFNtaWxlcwoKTW9zdCBhcnRpc3RzIGFwcGVhciBub3QgdG8gYmUgc21pbGluZy4KCmBgYHtyfQp3aXRoX2ZhY2VzICU+JSAKICBnZ3Bsb3QoYWVzKHggPSBmYWNlX3NtaWxlKSkgKyAKICBnZW9tX2hpc3RvZ3JhbShiaW53aWR0aCA9IDAuMSkgKwogIGxhYnModGl0bGUgPSAiU21pbGUgRGlzdHJpYnV0aW9uIikKYGBgCgpHZW5kZXIgZG9lc24ndCBzZWVtIHRvIG1hdHRlciBtdWNoOgoKYGBge3J9CndpdGhfZmFjZXMgJT4lIAogIGdncGxvdChhZXMoeCA9IGZhY2Vfc21pbGUsIGZpbGwgPSBmYWNlX2dlbmRlcikpICsgCiAgZ2VvbV9oaXN0b2dyYW0oYWVzKHkgPSAoLi5jb3VudC4uKS90YXBwbHkoLi5jb3VudC4uLC4uUEFORUwuLixzdW0pWy4uUEFORUwuLl0pLGJpbndpZHRoID0gMC4xICkgKyAKICAjZ2VvbV9oaXN0b2dyYW0oYWVzKHk9Li5jb3VudC4uL3N1bSguLmNvdW50Li4pKSwgYmlud2lkdGggPSAwLjEsIHBvc2l0aW9uID0gImRvZGdlIikgKwogIGZhY2V0X3dyYXAofiBmYWNlX2dlbmRlcikgKwogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBwZXJjZW50KSArCiAgbGFicyh0aXRsZSA9ICJTbWlsZSBEaXN0cmlidXRpb24gYnkgR2VuZGVyIiwgeSA9ICIiLCB4ID0gIiIpCmBgYAoKCgoKCmBgYHtyfQp3aXRoX2ZhY2VzICU+JSBtdXRhdGUoaXNfc21pbGluZyA9IGZhY2Vfc21pbGUgPiAwLjI1KSAlPiUKICBnZ3Bsb3QoYWVzKHggPSBpc19zbWlsaW5nLCBmaWxsID0gaXNfc21pbGluZykpICsgCiAgZ2VvbV9iYXIoYWVzKHkgPSAoLi5jb3VudC4uKS90YXBwbHkoLi5jb3VudC4uLC4uUEFORUwuLixzdW0pWy4uUEFORUwuLl0pICkgKyAKICAjZ2VvbV9oaXN0b2dyYW0oYWVzKHk9Li5jb3VudC4uL3N1bSguLmNvdW50Li4pKSwgYmlud2lkdGggPSAwLjEsIHBvc2l0aW9uID0gImRvZGdlIikgKwogIGZhY2V0X3dyYXAofiBmYWNlX2dlbmRlcikgKwogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBwZXJjZW50KSArCiAgbGFicyh0aXRsZSA9ICJTbWlsZSBEaXN0cmlidXRpb24gYnkgR2VuZGVyIiwgeSA9ICIiLCB4ID0gIiIpCmBgYAoKIyMgSW1hZ2UgU2l6ZXMKCgpgYGB7cn0KaW1nX2RldGFpbHNfZmlsZW5hbWUgPC0gImRhdGEvaW1hZ2VzLmNzdiIKaW1hZ2VzIDwtIHJlYWRfY3N2KGltZ19kZXRhaWxzX2ZpbGVuYW1lKQpgYGAKCmBgYHtyfQppbWFnZXMgJT4lCiAgZ2dwbG90KGFlcyh4ID0gaW1nX3dpZHRoKSkgKwogIGdlb21faGlzdG9ncmFtKGJpbndpZHRoID0gMTApICsKICBsYWJzKHRpdGxlID0gIkltYWdlIFdpZHRocyIpCiAgCmBgYAoKYGBge3J9CnN1bW1hcnkoaW1hZ2VzJGltZ193aWR0aCkKYGBgCgoKYGBge3J9CmltYWdlcyAlPiUKICBnZ3Bsb3QoYWVzKHggPSBpbWdfaGVpZ2h0KSkgKwogIGdlb21faGlzdG9ncmFtKGJpbndpZHRoID0gMTApICsKICBsYWJzKHRpdGxlID0gIkltYWdlIEhlaWdodCIpCiAgCmBgYAoKCmBgYHtyfQpzdW1tYXJ5KGltYWdlcyRpbWdfaGVpZ2h0KQpgYGAKCmBgYHtyfQppbWFnZXMgJT4lCiAgZ2dwbG90KGFlcyh4ID0gaW1nX3dpZHRoLCB5ID0gaW1nX2hlaWdodCkpICsKICBnZW9tX3BvaW50KCBhbHBoYSA9IDEgLyAxMCkgKwogIHN0YXRfZGVuc2l0eTJkKGFlcyhmaWxsID0gLi5sZXZlbC4uKSwgZ2VvbT0icG9seWdvbiIpCmBgYAoKIyMgRmFjZSBJbWFnZSBTaXplcwoKYGBge3J9CmZhY2VfZGV0YWlsc19maWxlbmFtZSA8LSAiZGF0YS9mYWNlX3NpemVzLmNzdiIKZmFjZV9kZXRhaWxzIDwtIHJlYWRfY3N2KGZhY2VfZGV0YWlsc19maWxlbmFtZSkKYGBgCgoKCgoKYGBge3J9CmZhY2VfZGV0YWlscyAlPiUKICBnZ3Bsb3QoYWVzKHggPSBpbWdfd2lkdGgsIHkgPSBpbWdfaGVpZ2h0KSkgKwogIGdlb21fcG9pbnQoIGFscGhhID0gMSAvIDEwKSArCiAgc3RhdF9kZW5zaXR5MmQoYWVzKGZpbGwgPSAuLmxldmVsLi4pLCBnZW9tPSJwb2x5Z29uIikKYGBgCgoKYGBge3J9CmZhY2VfZGV0YWlsc19ieV9hcnRpc3QgPC0gZmFjZV9kZXRhaWxzICU+JSBncm91cF9ieShjYXRfaWQsIGFydGlzdF9pZCkgJT4lIG11dGF0ZShmYWNlX2NvdW50ID0gbigpKSAlPiUgdW5ncm91cCgpCmBgYAoKCmBgYHtyfQpmYWNlX2RldGFpbHNfYnlfYXJ0aXN0ICU+JSBkaXN0aW5jdChjYXRfaWQsIGFydGlzdF9pZCwgLmtlZXBfYWxsID0gVFJVRSkgJT4lCiAgZ2dwbG90KGFlcyh4ID0gYXMuZmFjdG9yKGZhY2VfY291bnQpKSkgKyAKICBnZW9tX2JhcigpICsgCiAgbGFicyh0aXRsZSA9ICJDb3VudHMgb2YgQXJ0aXN0cyBieSBGYWNlIENvdW50IikKYGBgCgoKYGBge3J9CmZhY2VfZGV0YWlsc19ieV9hcnRpc3QgJT4lIAogIGdncGxvdChhZXMoeCA9IGFzLmZhY3RvcihmYWNlX2NvdW50KSwgeSA9IGltZ193aWR0aCkpICsgCiAgZ2VvbV9ib3hwbG90KCkgKyAKICBsYWJzKHRpdGxlID0gIkZhY2UgV2lkdGggYnkgRmFjZSBDb3VudCIpCmBgYAoKYGBge3J9CmZhY2VfZGV0YWlsc19ieV9hcnRpc3QgJT4lIAogIGdncGxvdChhZXMoeCA9IGFzLmZhY3RvcihmYWNlX2NvdW50KSwgeSA9IGltZ19oZWlnaHQpKSArIAogIGdlb21fYm94cGxvdCgpICsgCiAgbGFicyh0aXRsZSA9ICJGYWNlIEhlaWdodCBieSBGYWNlIENvdW50IikKCmBgYAoKCmBgYHtyfQoKZmFjZV9kZXRhaWxzX2J5X2FydGlzdCAlPiUgCiAgZ2dwbG90KGFlcyh4ID0gYXMuZmFjdG9yKGZhY2VfY291bnQpLCB5ID0gaW1nX2hlaWdodCkpICsgCiAgZ2VvbV9ib3hwbG90KCkgKyAKICBmYWNldF93cmFwKH4gY2F0X2lkKSArCiAgbGFicyh0aXRsZSA9ICJGYWNlIEhlaWdodCBieSBGYWNlIENvdW50IikKYGBgCgoKCgo=